package com.bestv.bsr.core.aspect;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bestv.bsr.core.entities.WebLog;
import com.bestv.bsr.core.utils.IpUtils;
import com.bestv.bsr.core.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

/**
 * todo 日志规范 接入长征 log
 *
 * @author taojiacheng
 */
@Component
@Aspect
@Slf4j(topic = "monitor")
public class WebLogAspect implements Ordered {

    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    public static final String TRACE_ID = "traceId";
    public static final String OTT_TRACE_TAG = "X-Ott-Trace";


    /**
     * 定义一个切点
     * controller中所有类的所有方法
     */
    @Pointcut("execution(* com.bestv.*.controller.*.*(..))")
    public void webLog() {
    }

    /**
     * 记录日志的环绕通知
     * 方法执行前后
     */
    @Around("webLog()")
    public Object recordWebLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        WebLog webLog = new WebLog();
        long start = System.currentTimeMillis();
        try {
            // 获取当前请求的request对象
            HttpServletRequest request = ServletUtils.getRequest();
            assert request != null;
            String traceId = MDC.get(TRACE_ID);
            if (StringUtils.isEmpty(traceId)) {
                traceId = StringUtils.isEmpty(request.getHeader(OTT_TRACE_TAG)) ?
                        RandomUtil.randomString(8) : request.getHeader(OTT_TRACE_TAG);
                MDC.put(TRACE_ID, traceId);
            }
            // 请求url
            String url = request.getRequestURL().toString();
            webLog.setUrl(url);
            webLog.setIp(IpUtils.getIpAddress(request));
            // 获取方法
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
            // 获取类名
            String targetClassName = proceedingJoinPoint.getTarget().getClass().getName();
            // 请求方法
            webLog.setMethod(targetClassName + "." + method.getName());
            // 请求参数
            String methodParams = getMethodParams(method, proceedingJoinPoint.getArgs());
            webLog.setParameter(methodParams);
            Object result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            webLog.setResult(JSON.toJSONString(result));
            return result;
        } catch (Throwable throwable) {
            webLog.setResult(throwable.getMessage());
            throw throwable;
        } finally {
            long end = System.currentTimeMillis();
            webLog.setSpendTime(end - start);
            log.info("method: {} | params: {} | cost: {} ｜ resp: {}| ip: {} | url: {}",
                    webLog.getMethod(), webLog.getParameter(), webLog.getSpendTime(),
                    webLog.getResult(), webLog.getIp(), webLog.getUrl()
            );
            MDC.clear();
        }
    }

    /**
     * 获取方法的参数 转换成json格式
     */
    private String getMethodParams(Method method, Object[] args) {
        JSONObject params = new JSONObject();
        // 方法的形参列表
        String[] parameterNames = DISCOVERER.getParameterNames(method);
        if (parameterNames != null) {
            for (int i = 0; i < args.length; i++) {
                if (isFilterObject(args[i])) {
                    params.put(parameterNames[i], "不支持此类型");
                } else {
                    params.put(parameterNames[i], args[i]);
                }
            }
        }
        return params.toJSONString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        if (o == null) {
            return true;
        }
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(InputStreamSource.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof InputStreamSource;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof InputStreamSource;
            }
        }
        return o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult || o instanceof InputStreamSource;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
